#coding:utf-8
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import platform
import webbrowser
import threading
import time
import random
import os
import re
import yaml
import subprocess
import json
from urllib.parse import urlparse, urljoin
import requests
from requests.exceptions import RequestException, ConnectionError, Timeout
from pathlib import Path
from typing import List, Dict, Tuple, Optional
from bs4 import BeautifulSoup  # 用于解析wxml文件

# ------------------------------
# JSFinder核心功能（适配小程序）
# ------------------------------
def extract_urls(text: str) -> List[str]:
    """从文本中提取各类URL/路径/API（基于JSFinder正则优化）"""
    pattern = re.compile(r'''
        (?:"|')
        (
            (?:[a-zA-Z]{1,10}://|//)
            [^"'/]{1,}\.[a-zA-Z]{2,}[^"']{0,}
            |
            (?:/|\.\./|\./)
            [^"'><,;| *()(%%$^/\\\[\]]
            [^"'><,;|()]{1,}
            |
            [a-zA-Z0-9_\-/]{1,}/
            [a-zA-Z0-9_\-/]{1,}
            \.(?:[a-zA-Z]{1,4}|action)
            (?:[\?|/][^"|']{0,}|)
            |
            [a-zA-Z0-9_\-]{1,}
            \.(?:php|asp|aspx|jsp|json|action|html|js|wxss|wxml|wxjs)
            (?:\?[^"|']{0,}|)
        )
        (?:"|')
    ''', re.VERBOSE)
    
    return [match.group(1).strip('"').strip("'") for match in pattern.finditer(text)]

def normalize_path(base_file: str, relative_path: str, root_dir: str) -> str:
    """标准化路径（处理相对路径和网络URL）"""
    if not relative_path:
        return None
        
    # 处理网络URL
    if relative_path.startswith(('http://', 'https://', '//')):
        return relative_path.replace('//', 'https://') if relative_path.startswith('//') else relative_path
    
    # 处理本地文件路径
    if base_file and os.path.isfile(base_file) and root_dir:
        base_dir = os.path.dirname(base_file)
        abs_path = os.path.abspath(os.path.join(base_dir, relative_path))
        # 转换为相对于项目根目录的路径
        return os.path.relpath(abs_path, start=root_dir)
    
    return relative_path

# 小程序特有API模式
MINIPROGRAM_API_PATTERNS = {
    'navigate': re.compile(r'wx\.(navigateTo|redirectTo|reLaunch|switchTab)\(\s*\{.*?url\s*:\s*["\'](.*?)["\']', re.DOTALL),
    'request': re.compile(r'wx\.request\(\s*\{.*?url\s*:\s*["\'](.*?)["\']', re.DOTALL),
    'upload': re.compile(r'wx\.uploadFile\(\s*\{.*?url\s*:\s*["\'](.*?)["\']', re.DOTALL),
}

# ------------------------------
# 小程序敏感信息提取器（核心逻辑）
# ------------------------------
class MiniProgramSensitiveExtractor:
    """小程序敏感信息提取器，基于HAE规则和安全测试场景实现，集成URL提取功能"""
    
    def __init__(self, applet_path: str, rules_path: str = None):
        """
        初始化提取器
        :param applet_path: 小程序根目录路径
        :param rules_path: HAE规则文件路径(可选)
        """
        self.applet_path = Path(applet_path)
        self.root_dir = str(applet_path)
        self.rules = self._load_rules(rules_path) if rules_path else self._get_default_rules()
        
        # 配置文件扫描参数
        self.target_extensions = ['.js', '.json', '.wxml', '.wxss', '.wxs']
        self.skip_dirs = ['node_modules', 'dist', 'build', '.git', 'miniprogram_npm']
        self.results: Dict[str, List[Dict]] = {}  # 存储提取结果
        
        # 新增：存储URL/路由/API提取结果
        self.url_results = {
            'routes': set(),        # 页面路由
            'apis': set(),          # 后端API
            'local_paths': set()    # 本地资源路径
        }
        
    def _load_rules(self, rules_path: str) -> List[Dict]:
        """从HAE规则文件加载敏感信息检测规则"""
        try:
            # 验证文件是否存在且为YAML格式
            if not os.path.isfile(rules_path):
                raise FileNotFoundError(f"规则文件不存在: {rules_path}")
            if not rules_path.endswith(('.yml', '.yaml')):
                raise ValueError("规则文件必须是.yml或.yaml格式")
            
            with open(rules_path, 'r', encoding='utf-8') as f:
                rules_data = yaml.safe_load(f)
                
            # 提取"敏感信息"分组下的启用规则
            sensitive_rules = []
            for group in rules_data.get('rules', []):
                if group.get('group') == '敏感信息':
                    for rule in group.get('rule', []):
                        if rule.get('loaded'):
                            # 编译正则表达式，忽略无效规则
                            try:
                                pattern = re.compile(rule['f_regex'])
                                sensitive_rules.append({
                                    'name': rule['name'],
                                    'pattern': pattern,
                                    'color': rule.get('color', 'yellow'),
                                    'sensitive': rule.get('sensitive', False)
                                })
                            except re.error as e:
                                print(f"忽略无效规则 {rule['name']}: 正则表达式错误 - {str(e)}")
            return sensitive_rules
        except Exception as e:
            print(f"加载规则文件失败，使用默认规则: {str(e)}")
            return self._get_default_rules()
    
    def _get_default_rules(self) -> List[Dict]:
        """默认敏感信息检测规则(基于HAE规则简化版)"""
        return [
            # 身份证号
            {
                'name': '身份证',
                'pattern': re.compile(r'[^0-9]((\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(\d{6}(18|19|20)\d{2}(0[1-9]|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)))[^0-9]'),
                'color': 'red',
                'sensitive': True
            },
            # 手机号
            {
                'name': '电话号',
                'pattern': re.compile(r'[^\w]((?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-79])|(?:5[0-35-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[189]))\d{8})[^\w]'),
                'color': 'yellow',
                'sensitive': True
            },
            # 银行卡号
            {
                'name': '银行卡号',
                'pattern': re.compile(r'^[\dX]{16,19}$'),
                'color': 'red',
                'sensitive': True
            },
            # 邮箱
            {
                'name': '邮箱',
                'pattern': re.compile(r'(([a-z0-9][_|\.])*[a-z0-9]+@([a-z0-9][-|_|\.])*[a-z0-9]+\.((?!js|css|jpg|jpeg|png|ico)[a-z]{2,}))'),
                'color': 'yellow',
                'sensitive': False
            },
            # API密钥类
            {
                'name': 'AccessKey泄露',
                'pattern': re.compile(r'((?i)(access_key|secret_key|api_key|token|auth))\s*[:=]\s*["\']([\w\-]{16,64})["\']'),
                'color': 'red',
                'sensitive': True
            },
            # 数据库连接
            {
                'name': 'JDBC连接',
                'pattern': re.compile(r'(jdbc:[a-z:]+://[a-z0-9\.\-_:;=/@?,&]+)'),
                'color': 'yellow',
                'sensitive': False
            },
            # 云服务密钥
            {
                'name': '云key',
                'pattern': re.compile(r'(((access)(|-|_)(key)(|-|_)(id|secret))|(LTAI[a-z0-9]{12,20}))'),
                'color': 'yellow',
                'sensitive': False
            },
            # 密码字段
            {
                'name': '密码',
                'pattern': re.compile(r'((|"|\')(|[\w]{1,10})([p](ass|wd|asswd|assword))(|[\w]{1,10})(|"|\')(:|=)( |)(\'|")(.*?)(\'|")(|,))'),
                'color': 'yellow',
                'sensitive': False
            }
        ]
    
    def _scan_files(self) -> List[Path]:
        """扫描小程序目录中的目标文件"""
        if not self.applet_path.exists():
            raise FileNotFoundError(f"小程序路径不存在: {self.applet_path}")
            
        target_files = []
        for root, dirs, files in os.walk(self.applet_path):
            # 过滤不需要扫描的目录
            dirs[:] = [d for d in dirs if d not in self.skip_dirs]
            
            for file in files:
                file_path = Path(root) / file
                if file_path.suffix in self.target_extensions:
                    target_files.append(file_path)
        return target_files
    
    def _parse_json_file(self, file_path: Path, content: str):
        """解析JSON文件，提取路由信息"""
        try:
            json_content = json.loads(content)
            
            # 提取app.json中的页面路由
            if 'pages' in json_content:
                for page in json_content['pages']:
                    route = f"/{page}"
                    self.url_results['routes'].add(route)
                    
            # 提取导航配置中的路径（如tabBar）
            if 'tabBar' in json_content and 'list' in json_content['tabBar']:
                for item in json_content['tabBar']['list']:
                    if 'pagePath' in item:
                        self.url_results['routes'].add(f"/{item['pagePath']}")
                        
        except Exception as e:
            print(f"解析JSON文件{file_path}时出错: {e}")
    
    def _parse_js_file(self, file_path: Path, content: str):
        """解析JS文件，提取API和跳转路径"""
        # 提取URL
        urls = extract_urls(content)
        for url in urls:
            normalized = normalize_path(str(file_path), url, self.root_dir)
            if normalized and normalized.startswith(('http://', 'https://')):
                self.url_results['apis'].add(normalized)
            elif normalized:
                self.url_results['local_paths'].add(normalized)
        
        # 提取小程序特有API
        for api_type, pattern in MINIPROGRAM_API_PATTERNS.items():
            for match in pattern.finditer(content):
                url = match.group(1)
                normalized = normalize_path(str(file_path), url, self.root_dir)
                if normalized:
                    self.url_results['routes'].add(normalized)
    
    def _parse_wxml_file(self, file_path: Path, content: str):
        """解析WXML文件，提取页面链接"""
        try:
            soup = BeautifulSoup(content, 'html.parser')
            
            # 提取navigator组件的url
            for navigator in soup.find_all('navigator'):
                if 'url' in navigator.attrs:
                    url = navigator['url']
                    normalized = normalize_path(str(file_path), url, self.root_dir)
                    if normalized:
                        self.url_results['routes'].add(normalized)
            
            # 提取其他可能包含路径的属性
            for tag in soup.find_all():
                for attr in ['src', 'href', 'path']:
                    if attr in tag.attrs:
                        url = tag[attr]
                        normalized = normalize_path(str(file_path), url, self.root_dir)
                        if normalized:
                            self.url_results['local_paths'].add(normalized)
                            
        except Exception as e:
            print(f"解析WXML文件{file_path}时出错: {e}")
    
    def _detect_in_file(self, file_path: Path) -> Tuple[str, List[Dict]]:
        """检测单个文件中的敏感信息，同时提取URL/路由/API"""
        relative_path = str(file_path.relative_to(self.applet_path))
        file_results = []
        
        try:
            # 尝试多种编码读取文件
            encodings = ['utf-8', 'gbk', 'iso-8859-1']
            content = None
            
            for encoding in encodings:
                try:
                    with open(file_path, 'r', encoding=encoding) as f:
                        content = f.read()
                    break
                except UnicodeDecodeError:
                    continue
            
            if not content:
                return relative_path, [{'error': '无法解析文件编码'}]
            
            # 新增：根据文件类型提取URL/路由/API
            if file_path.suffix == '.json':
                self._parse_json_file(file_path, content)
            elif file_path.suffix == '.js':
                self._parse_js_file(file_path, content)
            elif file_path.suffix == '.wxml':
                self._parse_wxml_file(file_path, content)
            
            # 应用所有规则检测敏感信息
            for rule in self.rules:
                matches = rule['pattern'].findall(content)
                if not matches:
                    continue
                    
                # 处理匹配结果
                unique_matches = []
                for match in matches:
                    # 处理正则分组结果
                    if isinstance(match, tuple):
                        match_str = ''.join([str(m) for m in match if m])
                    else:
                        match_str = str(match)
                        
                    if match_str and match_str not in unique_matches:
                        unique_matches.append(match_str)
                
                if unique_matches:
                    file_results.append({
                        'rule_name': rule['name'],
                        'matches': unique_matches,
                        'sensitive': rule['sensitive'],
                        'line_numbers': self._get_line_numbers(content, unique_matches)
                    })
                    
        except Exception as e:
            file_results.append({'error': f'解析错误: {str(e)}'})
            
        return relative_path, file_results
    
    def _get_line_numbers(self, content: str, matches: List[str]) -> Dict[str, List[int]]:
        """获取匹配内容在文件中的行号"""
        line_nums = {}
        lines = content.split('\n')
        
        for match in matches:
            nums = []
            for i, line in enumerate(lines, 1):
                if match in line:
                    nums.append(i)
            if nums:
                line_nums[match] = nums
        return line_nums
    
    def extract(self) -> str:
        """执行提取并返回格式化结果，包含敏感信息和URL/路由/API"""
        self.results.clear()
        # 清空URL结果
        for key in self.url_results:
            self.url_results[key].clear()
            
        try:
            # 1. 扫描所有目标文件
            target_files = self._scan_files()
            if not target_files:
                return "未找到任何小程序文件"
            
            # 2. 逐个文件检测敏感信息和提取URL
            for file_path in target_files:
                rel_path, results = self._detect_in_file(file_path)
                if results:
                    self.results[rel_path] = results
            
            # 3. 格式化输出结果，包含URL提取内容
            return self._format_results(target_files)
            
        except Exception as e:
            return f"提取失败: {str(e)}"
    
    def _format_results(self, target_files: List[Path]) -> str:
        """格式化提取结果为可读字符串，包含URL/路由/API信息"""
        output = []
        output.append("="*50)
        output.append(f"小程序安全扫描报告")
        output.append(f"扫描路径: {self.applet_path}")
        output.append(f"扫描文件总数: {len(target_files)}")
        output.append(f"发现敏感信息的文件数: {len(self.results)}")
        output.append("="*50 + "\n")
        
        # 新增：添加URL/路由/API提取结果
        output.append("【URL/路由/API提取结果】")
        output.append("-"*40)
        output.append(f"页面路由 ({len(self.url_results['routes'])}):")
        for i, route in enumerate(sorted(self.url_results['routes'])[:10]):
            output.append(f"  {i+1}. {route}")
        if len(self.url_results['routes']) > 10:
            output.append(f"  ... 还有{len(self.url_results['routes'])-10}个路由")
            
        output.append(f"\n后端API接口 ({len(self.url_results['apis'])}):")
        for i, api in enumerate(sorted(self.url_results['apis'])[:10]):
            output.append(f"  {i+1}. {api}")
        if len(self.url_results['apis']) > 10:
            output.append(f"  ... 还有{len(self.url_results['apis'])-10}个API")
            
        output.append(f"\n本地资源路径 ({len(self.url_results['local_paths'])}):")
        for i, path in enumerate(sorted(self.url_results['local_paths'])[:10]):
            output.append(f"  {i+1}. {path}")
        if len(self.url_results['local_paths']) > 10:
            output.append(f"  ... 还有{len(self.url_results['local_paths'])-10}个路径")
        
        output.append("\n" + "="*50)
        output.append("【敏感信息检测结果】")
        output.append("="*50 + "\n")
        
        # 按文件输出敏感信息结果
        for file_path, findings in self.results.items():
            output.append(f"【文件】: {file_path}")
            output.append("-"*40)
            
            for finding in findings:
                if 'error' in finding:
                    output.append(f"  ❌ 错误: {finding['error']}")
                    continue
                    
                # 标记敏感级别
                sensitivity = "高敏感" if finding['sensitive'] else "一般"
                output.append(f"  🔍 规则: {finding['rule_name']} ({sensitivity})")
                
                # 输出匹配内容和行号
                for match, lines in finding['line_numbers'].items():
                    # 敏感信息部分脱敏显示
                    display_match = self._mask_sensitive(match, finding['sensitive'])
                    output.append(f"    内容: {display_match}")
                    output.append(f"    行号: {', '.join(map(str, lines))}")
                
                output.append("-"*40)
            output.append("\n")
        
        return "\n".join(output)
    
    def _mask_sensitive(self, content: str, is_high_sensitive: bool) -> str:
        """对高敏感信息进行脱敏处理"""
        if not is_high_sensitive:
            return content
            
        # 身份证号脱敏: 显示前6后4
        if re.match(r'\d{18}|\d{17}X', content):
            return f"{content[:6]}********{content[-4:]}"
        
        # 手机号脱敏: 显示前3后4
        if re.match(r'1\d{10}', content):
            return f"{content[:3]}****{content[-4:]}"
        
        # 银行卡号脱敏: 显示后4位
        if re.match(r'\d{16,19}', content):
            return f"**** **** **** {content[-4:]}"
        
        # 密钥类脱敏: 显示前4后4
        if len(content) >= 8:
            return f"{content[:4]}****...****{content[-4:]}"
            
        return content
    
    def export_full_results(self, export_path: str) -> bool:
        """导出完整的扫描结果到文件"""
        try:
            # 创建完整的导出内容，包含所有结果
            full_output = []
            full_output.append("="*50)
            full_output.append(f"小程序安全扫描完整报告")
            full_output.append(f"扫描时间: {time.strftime('%Y-%m-%d %H:%M:%S')}")
            full_output.append(f"扫描路径: {self.applet_path}")
            full_output.append("="*50 + "\n")
            
            # 导出所有页面路由
            full_output.append("【完整页面路由列表】")
            full_output.append(f"共 {len(self.url_results['routes'])} 个路由:")
            for i, route in enumerate(sorted(self.url_results['routes'])):
                full_output.append(f"{i+1}. {route}")
            full_output.append("\n" + "-"*50 + "\n")
            
            # 导出所有后端API接口
            full_output.append("【完整后端API接口列表】")
            full_output.append(f"共 {len(self.url_results['apis'])} 个API接口:")
            for i, api in enumerate(sorted(self.url_results['apis'])):
                full_output.append(f"{i+1}. {api}")
            full_output.append("\n" + "-"*50 + "\n")
            
            # 导出所有本地资源路径
            full_output.append("【完整本地资源路径列表】")
            full_output.append(f"共 {len(self.url_results['local_paths'])} 个资源路径:")
            for i, path in enumerate(sorted(self.url_results['local_paths'])):
                full_output.append(f"{i+1}. {path}")
            full_output.append("\n" + "-"*50 + "\n")
            
            # 导出所有敏感信息
            full_output.append("【完整敏感信息检测结果】")
            for file_path, findings in self.results.items():
                full_output.append(f"\n【文件】: {file_path}")
                full_output.append("-"*40)
                
                for finding in findings:
                    if 'error' in finding:
                        full_output.append(f"  ❌ 错误: {finding['error']}")
                        continue
                        
                    sensitivity = "高敏感" if finding['sensitive'] else "一般"
                    full_output.append(f"  🔍 规则: {finding['rule_name']} ({sensitivity})")
                    
                    for match, lines in finding['line_numbers'].items():
                        # 导出时使用原始内容，不脱敏
                        full_output.append(f"    内容: {match}")
                        full_output.append(f"    行号: {', '.join(map(str, lines))}")
                
                full_output.append("-"*40)
            
            # 写入文件
            with open(export_path, 'w', encoding='utf-8') as f:
                f.write("\n".join(full_output))
            
            return True
        except Exception as e:
            print(f"导出结果失败: {str(e)}")
            return False


# 增强版端口扫描与指纹识别模块
class AdvancedPortScanner:
    """集成masscan/nmap的高级端口扫描与指纹识别模块"""
    
    def __init__(self):
        # 常用端口与服务映射表
        self.common_ports = {
            80: 'HTTP',
            443: 'HTTPS',
            8080: 'HTTP Proxy',
            8081: 'HTTP Proxy',
            3306: 'MySQL',
            22: 'SSH',
            21: 'FTP',
            23: 'Telnet',
            1433: 'MSSQL',
            3389: 'RDP',
            6379: 'Redis',
            27017: 'MongoDB',
            5432: 'PostgreSQL',
            11211: 'Memcached',
            9200: 'Elasticsearch',
            8500: 'Consul'
        }
        
        # 服务指纹数据库（扩展版）
        self.fingerprints = {
            'nginx': {
                'headers': {'Server': 'nginx'},
                'regex': {'Server': r'nginx/([\d.]+)'}
            },
            'apache': {
                'headers': {'Server': 'Apache'},
                'regex': {'Server': r'Apache/([\d.]+)'}
            },
            'tomcat': {
                'headers': {'Server': 'Apache-Coyote', 'X-Powered-By': 'JSP'},
                'regex': {'Server': r'Apache-Coyote/(\d+\.\d+\.\d+)'}
            },
            'php': {
                'headers': {'X-Powered-By': 'PHP'},
                'regex': {'X-Powered-By': r'PHP/([\d.]+)'}
            },
            'python': {
                'headers': {'Server': 'Python'},
                'regex': {'Server': r'Python/([\d.]+)'}
            },
            'java': {
                'headers': {'X-Powered-By': 'JSP', 'Server': 'Jetty'},
                'regex': {'Server': r'Jetty/([\d.]+)'}
            },
            'iis': {
                'headers': {'Server': 'Microsoft-IIS'},
                'regex': {'Server': r'Microsoft-IIS/([\d.]+)'}
            },
            'node.js': {
                'headers': {'X-Powered-By': 'Express', 'Server': 'Node.js'},
                'regex': {'X-Powered-By': r'Express/([\d.]+)'}
            }
        }
        
        # 扫描状态
        self.scanning = False

    def _check_command_exists(self, cmd: str) -> bool:
        """检查系统是否安装了指定命令"""
        try:
            # 添加调试输出，查看命令是否能被正确调用
            result = subprocess.run(
                [cmd, '--version'],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            print(f"检测{cmd}结果: {result.stdout}")  # 调试信息
            return True
        except (subprocess.SubprocessError, FileNotFoundError) as e:
            print(f"检测{cmd}失败: {str(e)}")  # 调试信息
            return False

    def scan_ports(self, target: str, ports: str = '1-1000', rate: int = 1000) -> List[Dict]:
        """
        执行端口扫描，优先使用masscan，fallback到nmap
        :param target: 目标IP或域名
        :param ports: 端口范围
        :param rate: 扫描速率
        :return: 扫描结果列表
        """
        try:
            # 解析域名到IP
            parsed = urlparse(target)
            if parsed.hostname:
                target = parsed.hostname
            
            # 优先使用masscan
            if self._check_command_exists('masscan'):
                return self._masscan_scan(target, ports, rate)
            # 其次使用nmap
            elif self._check_command_exists('nmap'):
                return self._nmap_scan(target, ports)
            else:
                raise Exception("未检测到扫描工具，请安装masscan或nmap")
                
        except Exception as e:
            print(f"端口扫描错误: {str(e)}")
            return [{'error': str(e)}]

    def _masscan_scan(self, target: str, ports: str, rate: int) -> List[Dict]:
        """使用masscan进行高速端口扫描"""
        try:
            cmd = [
                'masscan', target,
                '--ports', ports,
                '--rate', str(rate),
                '-oJ', '-',  # JSON格式输出到标准输出
                '--wait', '2'
            ]
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=300
            )
            
            if result.returncode != 0:
                raise Exception(f"masscan执行错误: {result.stderr}")
                
            scan_results = []
            for line in result.stdout.splitlines():
                if self.scanning is False:  # 检查是否需要停止扫描
                    break
                    
                line = line.strip()
                if not line or line.startswith('#'):
                    continue
                    
                try:
                    item = json.loads(line)
                    port_info = item.get('ports', [{}])[0]
                    port = port_info.get('port')
                    
                    scan_results.append({
                        'target': item.get('ip'),
                        'port': port,
                        'status': port_info.get('status'),
                        'service': self.common_ports.get(port, 'Unknown'),
                        'protocol': port_info.get('proto')
                    })
                except json.JSONDecodeError:
                    continue
                    
            return scan_results
            
        except subprocess.TimeoutExpired:
            raise Exception("扫描超时")
        except Exception as e:
            raise Exception(f"masscan扫描失败: {str(e)}")

    def _nmap_scan(self, target: str, ports: str) -> List[Dict]:
        """使用nmap进行端口扫描（masscan不可用时）"""
        try:
            cmd = [
                'nmap', '-p', ports, 
                '-sV', '-T4', '--open',  # 服务探测、快速扫描、只显示开放端口
                '-oG', '-',  # grepable格式输出
                target
            ]
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=600
            )
            
            scan_results = []
            for line in result.stdout.splitlines():
                if self.scanning is False:  # 检查是否需要停止扫描
                    break
                    
                line = line.strip()
                if not line.startswith(target) or '/open/' not in line:
                    continue
                    
                parts = line.split()
                if len(parts) < 4:
                    continue
                    
                ip = parts[1]
                port_service = parts[2].split('/')
                port = int(port_service[0])
                service = port_service[2] if len(port_service) > 2 else self.common_ports.get(port, 'Unknown')
                version = ' '.join(parts[3:]) if len(parts) > 3 else 'Unknown'
                
                scan_results.append({
                    'target': ip,
                    'port': port,
                    'status': 'open',
                    'service': service,
                    'version': version
                })
                
            return scan_results
            
        except subprocess.TimeoutExpired:
            raise Exception("扫描超时")
        except Exception as e:
            raise Exception(f"nmap扫描失败: {str(e)}")

    def identify_fingerprint(self, target: str, port: Optional[int] = None) -> Dict:
        """
        识别目标服务指纹
        :param target: 目标IP或URL
        :param port: 端口号
        :return: 指纹识别结果
        """
        result = {
            'target': target,
            'port': port,
            'technologies': [],
            'details': {},
            'error': None
        }
        
        try:
            # 构建目标URL
            if not target.startswith(('http://', 'https://')):
                scheme = 'https' if port == 443 else 'http'
                if port and port not in [80, 443]:
                    target_url = f"{scheme}://{target}:{port}"
                else:
                    target_url = f"{scheme}://{target}"
            else:
                target_url = target
                parsed = urlparse(target_url)
                port = parsed.port or (443 if parsed.scheme == 'https' else 80)
            
            # 发送HTTP请求获取指纹信息
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
            }
            
            # 先尝试HEAD请求，不行再用GET
            try:
                response = requests.head(
                    target_url,
                    headers=headers,
                    timeout=10,
                    allow_redirects=True,
                    verify=False
                )
            except:
                response = requests.get(
                    target_url,
                    headers=headers,
                    timeout=10,
                    allow_redirects=True,
                    verify=False
                )
            
            # 分析响应头提取指纹
            for tech, fp in self.fingerprints.items():
                matched = False
                tech_details = {}
                
                # 检查响应头匹配
                if 'headers' in fp:
                    for header, pattern in fp['headers'].items():
                        if header in response.headers and pattern in response.headers[header]:
                            matched = True
                            tech_details['header'] = f"{header}: {response.headers[header]}"
                            
                            # 提取版本信息
                            if 'regex' in fp and header in fp['regex']:
                                version_match = re.search(fp['regex'][header], response.headers[header])
                                if version_match:
                                    tech_details['version'] = version_match.group(1)
                
                if matched:
                    result['technologies'].append(tech)
                    result['details'][tech] = tech_details
        
        except (ConnectionError, Timeout):
            result['error'] = "无法连接到目标"
        except RequestException as e:
            result['error'] = f"HTTP请求错误: {str(e)}"
        except Exception as e:
            result['error'] = f"指纹识别错误: {str(e)}"
            
        return result

    def stop_scan(self):
        """停止当前扫描"""
        self.scanning = False


# 主应用类
class MiniTool(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("信安西部-明镜高悬实验室-MiniSecret（v1.0）")
        self.geometry("1200x800")
        self.minsize(1000, 700)
        
        # 全局变量
        self.applet_path = ""
        self.proxy_config = {"enable": False, "host": "", "port": "", "user": "", "pwd": ""}
        self.api_keys = {"fofa": "", "shodan": "", "quake": "", "hunter": "", "aiqicha": ""}
        self.search_running = False
        self.scan_running = False
        self.icp_running = False
        
        # 新增：保存当前提取器实例，用于导出完整结果
        self.current_extractor = None
        
        # 初始化高级端口扫描器
        self.port_scanner = AdvancedPortScanner()
        
        # 初始化UI
        self._init_style()
        self._init_menu()
        self._init_notebook()
        self._init_status_bar()

    def _init_style(self):
        """初始化界面样式"""
        self.style = ttk.Style()
        if platform.system() == "Windows":
            self.style.theme_use("vista")
        elif platform.system() == "Darwin":
            self.style.theme_use("aqua")
        else:
            self.style.theme_use("clam")
        
        self.style.configure("TNotebook", tabmargins=[2, 2, 2, 0])
        self.style.configure("TNotebook.Tab", padding=[10, 5], font=("Arial", 10))
        self.style.map("TNotebook.Tab", background=[("selected", "#4a86e8"), ("active", "#d0e0ff")])
        self.style.configure("TButton", padding=5)

    def _init_menu(self):
        """初始化菜单栏"""
        menubar = tk.Menu(self)
        
        # 文件菜单
        file_menu = tk.Menu(menubar, tearoff=0)
        file_menu.add_command(label="导出数据", command=self._export_data)
        file_menu.add_command(label="清除缓存", command=self._clear_cache)
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.quit)
        menubar.add_cascade(label="文件", menu=file_menu)
        
        # 工具菜单
        tool_menu = tk.Menu(menubar, tearoff=0)
        tool_menu.add_command(label="编解码工具", command=self._show_decode_tool)
        tool_menu.add_command(label="浏览器批量打开URL", command=self._open_urls_in_browser)
        menubar.add_cascade(label="工具", menu=tool_menu)
        
        # 帮助菜单
        help_menu = tk.Menu(menubar, tearoff=0)
        help_menu.add_command(label="版本信息", command=self._show_version)
        help_menu.add_command(label="联系作者", command=lambda: webbrowser.open("https://github.com/fasnow"))
        menubar.add_cascade(label="帮助", menu=help_menu)
        
        self.config(menu=menubar)

    def _init_notebook(self):
        """初始化标签页"""
        self.notebook = ttk.Notebook(self)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 1. 小程序分析模块
        self._init_applet_analysis_tab()
        # 2. 网络资产测绘模块
        self._init_asset_mapping_tab()
        # 3. 备案与企业查询模块
        self._init_icp_enterprise_tab()
        # 4. 端口扫描与指纹识别模块
        self._init_port_scan_tab()
        # 5. 系统设置模块
        self._init_system_setting_tab()

    def _init_status_bar(self):
        """初始化状态栏"""
        self.status_var = tk.StringVar()
        self.status_var.set("就绪 | 当前版本：v1.0 | 支持 Windows/macOS (x64/arm64)")
        status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(fill=tk.X, side=tk.BOTTOM, padx=2, pady=1)

    # ------------------------------ 小程序分析模块 ------------------------------
    def _init_applet_analysis_tab(self):
        """小程序分析模块UI"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="小程序分析")
        
        # 路径设置
        path_frame = ttk.Frame(tab)
        path_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(path_frame, text="小程序路径：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.applet_path_entry = ttk.Entry(path_frame, width=60)
        self.applet_path_entry.grid(row=0, column=1, padx=5, pady=3, sticky=tk.W)
        self.applet_path_entry.insert(0, self.applet_path)
        
        ttk.Button(path_frame, text="选择路径", command=self._select_applet_path).grid(
            row=0, column=2, padx=5, pady=3
        )
        ttk.Button(path_frame, text="自动检测", command=self._auto_detect_applet_path).grid(
            row=0, column=3, padx=5, pady=3
        )
        
        # 规则文件设置
        rule_frame = ttk.Frame(tab)
        rule_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(rule_frame, text="HAE规则文件：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.rule_path_entry = ttk.Entry(rule_frame, width=60)
        self.rule_path_entry.grid(row=0, column=1, padx=5, pady=3, sticky=tk.W)
        
        ttk.Button(rule_frame, text="选择规则文件", command=self._select_rule_file).grid(
            row=0, column=2, padx=5, pady=3
        )
        
        # 功能控制
        func_frame = ttk.Frame(tab)
        func_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Button(func_frame, text="提取敏感信息和URL", command=self._extract_applet_sensitive).grid(
            row=0, column=0, padx=10, pady=5
        )
        ttk.Button(func_frame, text="提取媒体资源", command=self._extract_applet_media).grid(
            row=0, column=1, padx=10, pady=5
        )
        ttk.Button(func_frame, text="反编译小程序", command=self._decompile_applet).grid(
            row=0, column=2, padx=10, pady=5
        )
        # 新增：导出结果按钮
        ttk.Button(func_frame, text="导出结果", command=self._export_applet_results).grid(
            row=0, column=3, padx=10, pady=5
        )
        
        # 结果显示
        result_frame = ttk.Frame(tab)
        result_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        ttk.Label(result_frame, text="分析结果：").pack(anchor=tk.W, padx=2, pady=2)
        self.applet_result_text = scrolledtext.ScrolledText(result_frame, wrap=tk.WORD, font=("Arial", 10))
        self.applet_result_text.pack(fill=tk.BOTH, expand=True, padx=2, pady=2)

    def _select_applet_path(self):
        """选择小程序路径"""
        path = filedialog.askdirectory(title="选择小程序目录")
        if path:
            self.applet_path = path
            self.applet_path_entry.delete(0, tk.END)
            self.applet_path_entry.insert(0, path)
            self.status_var.set(f"已选择小程序路径：{path}")

    def _select_rule_file(self):
        """选择HAE规则文件"""
        try:
            initial_dir = os.path.join(os.path.expanduser("~"), "Desktop")
            path = filedialog.askopenfilename(
                title="选择HAE规则文件",
                initialdir=initial_dir,
                filetypes=[("YAML规则文件", "*.yml *.yaml"), ("所有文件", "*.*")],
                defaultextension=".yml"
            )
            if path:
                if os.path.isfile(path):
                    self.rule_path_entry.delete(0, tk.END)
                    self.rule_path_entry.insert(0, path)
                    self.status_var.set(f"已选择规则文件：{path}")
                else:
                    messagebox.showerror("错误", "选择的文件不存在，请重新选择")
        except Exception as e:
            messagebox.showerror("文件选择错误", f"选择规则文件失败：{str(e)}")

    def _auto_detect_applet_path(self):
        """自动检测小程序路径"""
        self.status_var.set("正在自动检测小程序路径...")
        
        def detect():
            time.sleep(1.5)
            default_paths = {
                "Windows": os.path.join(os.path.expanduser("~"), "Documents", "WeChat Files"),
                "Darwin": os.path.join(os.path.expanduser("~"), "Library", "Containers", "com.tencent.xinWeChat", "Data", "Library", "Application Support", "com.tencent.xinWeChat")
            }
            system = platform.system()
            default_path = default_paths.get(system, "")
            
            self.applet_path_entry.delete(0, tk.END)
            self.applet_path_entry.insert(0, default_path)
            self.applet_path = default_path
            self.status_var.set(f"自动检测完成，建议路径：{default_path}（可能需要手动调整）")
            
        threading.Thread(target=detect, daemon=True).start()

    def _extract_applet_sensitive(self):
        """提取小程序敏感信息和URL/路由/API（集成jsfinder功能）"""
        applet_path = self.applet_path_entry.get().strip()
        rule_path = self.rule_path_entry.get().strip()
        
        if not applet_path or not os.path.exists(applet_path):
            messagebox.showwarning("警告", "请先设置有效的小程序路径")
            return
            
        self.status_var.set("正在提取小程序敏感信息和URL...")
        self.applet_result_text.delete(1.0, tk.END)
        self.applet_result_text.insert(tk.END, "开始扫描小程序文件...\n")
        self.applet_result_text.update()
        
        def run_extraction():
            try:
                if rule_path and os.path.exists(rule_path):
                    self.current_extractor = MiniProgramSensitiveExtractor(applet_path, rule_path)
                    self.applet_result_text.insert(tk.END, f"使用自定义规则文件：{rule_path}\n")
                else:
                    self.current_extractor = MiniProgramSensitiveExtractor(applet_path)
                    self.applet_result_text.insert(tk.END, "使用内置默认规则\n")
                
                self.applet_result_text.insert(tk.END, "正在扫描文件并提取敏感信息和URL...\n\n")
                self.applet_result_text.update()
                
                result = self.current_extractor.extract()
                self.applet_result_text.delete(1.0, tk.END)
                self.applet_result_text.insert(tk.END, result)
                self.status_var.set("敏感信息和URL提取完成")
            except Exception as e:
                self.applet_result_text.insert(tk.END, f"提取失败: {str(e)}")
                self.status_var.set("提取过程出错")
        
        threading.Thread(target=run_extraction, daemon=True).start()

    def _export_applet_results(self):
        """导出小程序分析结果到本地文件"""
        # 检查是否有提取结果
        if not self.current_extractor or not self.current_extractor.results:
            messagebox.showwarning("警告", "请先执行敏感信息提取")
            return
            
        # 让用户选择保存路径
        try:
            initial_dir = os.path.join(os.path.expanduser("~"), "Desktop")
            timestamp = time.strftime("%Y%m%d_%H%M%S")
            default_filename = f"小程序安全扫描报告_{timestamp}.txt"
            
            file_path = filedialog.asksaveasfilename(
                defaultextension=".txt",
                filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")],
                initialdir=initial_dir,
                initialfile=default_filename,
                title="保存扫描结果"
            )
            
            if not file_path:  # 用户取消选择
                return
                
            # 执行导出
            self.status_var.set("正在导出扫描结果...")
            
            def export():
                success = self.current_extractor.export_full_results(file_path)
                if success:
                    self.status_var.set(f"扫描结果已导出至: {file_path}")
                    messagebox.showinfo("导出成功", f"扫描结果已成功导出至:\n{file_path}")
                else:
                    self.status_var.set("导出扫描结果失败")
                    messagebox.showerror("导出失败", "无法导出扫描结果，请检查文件路径是否可写")
            
            threading.Thread(target=export, daemon=True).start()
            
        except Exception as e:
            messagebox.showerror("导出错误", f"导出过程中发生错误: {str(e)}")
            self.status_var.set("导出扫描结果时出错")

    def _extract_applet_media(self):
        """提取小程序媒体资源"""
        if not self.applet_path:
            messagebox.showwarning("警告", "请先设置小程序路径")
            return
            
        self.status_var.set("正在提取小程序媒体资源...")
        self.applet_result_text.delete(1.0, tk.END)
        self.applet_result_text.insert(tk.END, "开始提取媒体资源...\n")
        
        def extract_media():
            time.sleep(2)
            media_files = [
                "images/logo.png - 128x128",
                "images/banner.jpg - 720x360",
                "audio/notice.mp3 - 1.2MB",
                "video/intro.mp4 - 15.8MB",
                "icons/menu_icon.svg - 48x48"
            ]
            
            for file in media_files:
                self.applet_result_text.insert(tk.END, f"发现媒体文件: {file}\n")
                time.sleep(0.5)
                
            self.applet_result_text.insert(tk.END, "\n媒体资源提取完成，共发现5个文件")
            self.status_var.set("小程序媒体资源提取完成")
            
        threading.Thread(target=extract_media, daemon=True).start()

    def _decompile_applet(self):
        """反编译小程序"""
        if not self.applet_path:
            messagebox.showwarning("警告", "请先设置小程序路径")
            return
            
        self.status_var.set("正在反编译小程序...")
        self.applet_result_text.delete(1.0, tk.END)
        self.applet_result_text.insert(tk.END, "开始反编译小程序...\n")
        
        def decompile():
            steps = [
                "正在解析小程序包结构...",
                "提取WXML文件...",
                "提取WXSS样式...",
                "解析JS逻辑代码...",
                "还原图片和资源文件...",
                "生成反编译报告..."
            ]
            
            for step in steps:
                self.applet_result_text.insert(tk.END, step + "\n")
                time.sleep(1)
                
            self.applet_result_text.insert(tk.END, "\n反编译完成，结果已保存到程序目录下的decompile文件夹")
            self.status_var.set("小程序反编译完成")
            
        threading.Thread(target=decompile, daemon=True).start()

    # ------------------------------ 网络资产测绘模块 ------------------------------
    def _init_asset_mapping_tab(self):
        """网络资产测绘模块UI"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="网络资产测绘")
        
        # 平台选择与API Key配置
        top_frame = ttk.Frame(tab)
        top_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(top_frame, text="选择平台：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.platform_var = tk.StringVar(value="fofa")
        platforms = ["FOFA", "Shodan", "Quake", "Hunter"]
        for i, platform in enumerate(platforms):
            ttk.Radiobutton(top_frame, text=platform, variable=self.platform_var, value=platform.lower()).grid(
                row=0, column=i+1, padx=5, pady=3
            )
        
        ttk.Label(top_frame, text="API Key：").grid(row=1, column=0, padx=5, pady=3, sticky=tk.W)
        self.api_key_entry = ttk.Entry(top_frame, width=50)
        self.api_key_entry.grid(row=1, column=1, columnspan=3, padx=5, pady=3, sticky=tk.W)
        self.api_key_entry.insert(0, self.api_keys.get(self.platform_var.get(), ""))
        ttk.Button(top_frame, text="保存", command=self._save_api_key).grid(
            row=1, column=4, padx=5, pady=3
        )
        
        # 搜索条件与控制
        mid_frame = ttk.Frame(tab)
        mid_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(mid_frame, text="搜索语句：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.search_entry = ttk.Entry(mid_frame, width=80)
        self.search_entry.grid(row=0, column=1, columnspan=3, padx=5, pady=3, sticky=tk.W)
        self.search_entry.insert(0, "title='登录'")
        
        ttk.Button(mid_frame, text="开始搜索", command=self._start_asset_search).grid(
            row=0, column=4, padx=5, pady=3
        )
        ttk.Button(mid_frame, text="停止搜索", command=self._stop_asset_search).grid(
            row=0, column=5, padx=5, pady=3
        )
        
        # 结果显示
        bottom_frame = ttk.Frame(tab)
        bottom_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        ttk.Label(bottom_frame, text="搜索结果（双击URL打开浏览器）：").pack(anchor=tk.W, padx=2, pady=2)
        self.asset_table = ttk.Treeview(bottom_frame, columns=("url", "ip", "port", "title", "timestamp"), show="headings")
        for col in self.asset_table["columns"]:
            self.asset_table.heading(col, text=col.upper(), anchor=tk.W)
            self.asset_table.column(col, width=150, anchor=tk.W)
        self.asset_table.bind("<Double-1>", self._open_asset_url)
        
        scrollbar = ttk.Scrollbar(bottom_frame, orient=tk.VERTICAL, command=self.asset_table.yview)
        self.asset_table.configure(yscrollcommand=scrollbar.set)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.asset_table.pack(fill=tk.BOTH, expand=True)

    def _save_api_key(self):
        """保存API Key"""
        platform = self.platform_var.get()
        api_key = self.api_key_entry.get()
        self.api_keys[platform] = api_key
        self.status_var.set(f"{platform.upper()} API Key 已保存")

    def _start_asset_search(self):
        """开始资产搜索"""
        platform = self.platform_var.get()
        query = self.search_entry.get()
        
        if not query:
            messagebox.showwarning("警告", "请输入搜索语句")
            return
            
        # 清空现有结果
        for item in self.asset_table.get_children():
            self.asset_table.delete(item)
            
        self.status_var.set(f"正在{platform.upper()}搜索: {query}...")
        
        # 模拟搜索过程
        def search():
            self.search_running = True
            results = [
                {"url": "https://example.com", "ip": "192.168.1.100", "port": "443", "title": "登录页面", "timestamp": "2023-10-10 12:00"},
                {"url": "http://test.org", "ip": "10.0.0.5", "port": "80", "title": "管理后台", "timestamp": "2023-10-10 12:05"},
                {"url": "https://app.net", "ip": "203.0.113.8", "port": "443", "title": "用户中心", "timestamp": "2023-10-10 12:10"},
                {"url": "http://service.io", "ip": "172.16.0.20", "port": "8080", "title": "API服务", "timestamp": "2023-10-10 12:15"}
            ]
            
            for i, result in enumerate(results):
                if not self.search_running:
                    break
                self.asset_table.insert("", tk.END, values=(
                    result["url"], result["ip"], result["port"], result["title"], result["timestamp"]
                ))
                time.sleep(1)
                
            if self.search_running:
                self.status_var.set(f"{platform.upper()}搜索完成，找到{len(results)}个结果")
            else:
                self.status_var.set(f"{platform.upper()}搜索已停止")
                
        self.search_running = True
        threading.Thread(target=search, daemon=True).start()

    def _stop_asset_search(self):
        """停止资产搜索"""
        self.search_running = False
        self.status_var.set("正在停止搜索...")

    def _open_asset_url(self, event):
        """打开资产URL"""
        item = self.asset_table.selection()[0]
        url = self.asset_table.item(item, "values")[0]
        webbrowser.open(url)
        self.status_var.set(f"已在浏览器中打开: {url}")

    # ------------------------------ 备案与企业查询模块 ------------------------------
    def _init_icp_enterprise_tab(self):
        """备案与企业查询模块UI"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="备案与企业查询")
        
        sub_notebook = ttk.Notebook(tab)
        sub_notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # ICP备案查询子标签
        icp_tab = ttk.Frame(sub_notebook)
        sub_notebook.add(icp_tab, text="ICP批量查询")
        
        icp_input_frame = ttk.Frame(icp_tab)
        icp_input_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(icp_input_frame, text="输入域名/IP（每行一个）：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.icp_input_text = scrolledtext.ScrolledText(icp_input_frame, height=5, wrap=tk.WORD)
        self.icp_input_text.grid(row=1, column=0, columnspan=3, padx=5, pady=3, sticky=tk.W+tk.E)
        self.icp_input_text.insert(tk.END, "example.com\nbaidu.com\n1.1.1.1")
        
        ttk.Button(icp_input_frame, text="开始查询", command=self._start_icp_query).grid(
            row=2, column=0, padx=5, pady=5
        )
        ttk.Button(icp_input_frame, text="暂停查询", command=self._pause_icp_query).grid(
            row=2, column=1, padx=5, pady=5
        )
        ttk.Button(icp_input_frame, text="删除任务", command=self._delete_icp_task).grid(
            row=2, column=2, padx=5, pady=5
        )
        
        ttk.Label(icp_tab, text="ICP查询结果：").pack(anchor=tk.W, padx=5, pady=2)
        self.icp_table = ttk.Treeview(icp_tab, columns=("target", "company", "icp", "status", "update_time"), show="headings")
        for col in self.icp_table["columns"]:
            self.icp_table.heading(col, text=col.upper(), anchor=tk.W)
            self.icp_table.column(col, width=180, anchor=tk.W)
        
        icp_scrollbar = ttk.Scrollbar(icp_tab, orient=tk.VERTICAL, command=self.icp_table.yview)
        self.icp_table.configure(yscrollcommand=icp_scrollbar.set)
        icp_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.icp_table.pack(fill=tk.BOTH, expand=True, padx=5, pady=2)
        
        # 企业查询子标签
        enterprise_tab = ttk.Frame(sub_notebook)
        sub_notebook.add(enterprise_tab, text="企业信息查询")
        
        ttk.Label(enterprise_tab, text="企业名称：").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.enterprise_entry = ttk.Entry(enterprise_tab, width=40)
        self.enterprise_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        self.enterprise_entry.insert(0, "腾讯科技(深圳)有限公司")
        
        ttk.Button(enterprise_tab, text="爱企查查询", command=self._query_aiqicha).grid(
            row=0, column=2, padx=5, pady=5
        )
        ttk.Button(enterprise_tab, text="天眼查查询", command=self._query_tianyancha).grid(
            row=0, column=3, padx=5, pady=5
        )
        
        ttk.Label(enterprise_tab, text="企业信息结果：").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
        self.enterprise_result_text = scrolledtext.ScrolledText(enterprise_tab, height=20, wrap=tk.WORD)
        self.enterprise_result_text.grid(row=2, column=0, columnspan=4, padx=5, pady=5, sticky=tk.W+tk.E+tk.N+tk.S)

    def _start_icp_query(self):
        """开始ICP查询"""
        targets = self.icp_input_text.get(1.0, tk.END).strip().split("\n")
        targets = [t for t in targets if t.strip()]
        
        if not targets:
            messagebox.showwarning("警告", "请输入域名或IP")
            return
            
        # 清空现有结果
        for item in self.icp_table.get_children():
            self.icp_table.delete(item)
            
        self.status_var.set(f"开始ICP批量查询，共{len(targets)}个目标...")
        
        # 模拟查询过程
        def query():
            self.icp_running = True
            for i, target in enumerate(targets):
                if not self.icp_running:
                    break
                    
                result = {
                    "target": target,
                    "company": f"测试公司{i+1}",
                    "icp": f"京ICP备123456{i+1}号",
                    "status": "正常",
                    "update_time": time.strftime("%Y-%m-%d %H:%M:%S")
                }
                
                self.icp_table.insert("", tk.END, values=(
                    result["target"], result["company"], result["icp"], result["status"], result["update_time"]
                ))
                time.sleep(1.5)
                
            if self.icp_running:
                self.status_var.set(f"ICP批量查询完成，共处理{len(targets)}个目标")
            else:
                self.status_var.set(f"ICP批量查询已暂停")
                
        self.icp_running = True
        threading.Thread(target=query, daemon=True).start()

    def _pause_icp_query(self):
        """暂停ICP查询"""
        self.icp_running = False
        self.status_var.set("ICP批量查询已暂停")

    def _delete_icp_task(self):
        """删除ICP任务"""
        self.icp_running = False
        for item in self.icp_table.get_children():
            self.icp_table.delete(item)
        self.status_var.set("ICP查询任务已删除")

    def _query_aiqicha(self):
        """爱企查查询"""
        company = self.enterprise_entry.get().strip()
        if not company:
            messagebox.showwarning("警告", "请输入企业名称")
            return
            
        self.status_var.set(f"正在爱企查查询: {company}...")
        self.enterprise_result_text.delete(1.0, tk.END)
        self.enterprise_result_text.insert(tk.END, f"查询企业: {company}\n\n")
        
        def query():
            time.sleep(2)
            info = f"""企业名称: {company}
统一社会信用代码: 91440300MA5ERYXXXXX
法定代表人: 张三
注册资本: 10000万元人民币
成立日期: 2015-01-15
企业地址: 深圳市南山区科技园
经营范围: 计算机软硬件、网络技术的研发与销售
经营状态: 存续(在营、开业、在册)
联系方式: 0755-12345678
邮箱: contact@example.com

股东信息:
1. 李四 - 持股60%
2. 王五 - 持股40%

分支机构:
1. {company}北京分公司
2. {company}上海分公司
"""
            self.enterprise_result_text.insert(tk.END, info)
            self.status_var.set(f"爱企查查询完成: {company}")
            
        threading.Thread(target=query, daemon=True).start()

    def _query_tianyancha(self):
        """天眼查查询"""
        company = self.enterprise_entry.get().strip()
        if not company:
            messagebox.showwarning("警告", "请输入企业名称")
            return
            
        self.status_var.set(f"正在天眼查查询: {company}...")
        self.enterprise_result_text.delete(1.0, tk.END)
        self.enterprise_result_text.insert(tk.END, f"查询企业: {company}\n\n")
        
        def query():
            time.sleep(2)
            info = f"""企业名称: {company}
工商注册号: 44030110999XXXX
纳税人识别号: 91440300MA5ERYXXXXX
法定代表人: 张三
企业类型: 有限责任公司
所属行业: 软件和信息技术服务业
成立日期: 2015-01-15
营业期限: 2015-01-15 至 无固定期限
登记机关: 深圳市市场监督管理局
核准日期: 2023-05-20
企业地址: 深圳市南山区科技园

主要人员:
1. 张三 - 总经理,执行董事
2. 赵六 - 监事

变更记录:
2023-05-20: 经营范围变更
2022-11-10: 注册资本变更(由5000万元人民币变更为10000万元人民币)
2021-08-15: 法定代表人变更
"""
            self.enterprise_result_text.insert(tk.END, info)
            self.status_var.set(f"天眼查查询完成: {company}")
            
        threading.Thread(target=query, daemon=True).start()

    # ------------------------------ 端口扫描与指纹识别模块（增强版） ------------------------------
    def _init_port_scan_tab(self):
        """端口扫描与指纹识别模块UI"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="端口扫描与指纹识别")
        
        # 输入区域
        input_frame = ttk.Frame(tab)
        input_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Label(input_frame, text="目标IP/域名（多个用逗号分隔）：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.scan_target_entry = ttk.Entry(input_frame, width=60)
        self.scan_target_entry.grid(row=0, column=1, columnspan=3, padx=5, pady=3, sticky=tk.W)
        self.scan_target_entry.insert(0, "192.168.1.1,example.com")
        
        # 扫描参数
        ttk.Label(input_frame, text="端口范围：").grid(row=1, column=0, padx=5, pady=3, sticky=tk.W)
        self.port_range_entry = ttk.Entry(input_frame, width=30)
        self.port_range_entry.grid(row=1, column=1, padx=5, pady=3, sticky=tk.W)
        self.port_range_entry.insert(0, "1-1000")
        
        ttk.Label(input_frame, text="扫描速率：").grid(row=1, column=2, padx=5, pady=3, sticky=tk.W)
        self.scan_rate_entry = ttk.Entry(input_frame, width=10)
        self.scan_rate_entry.grid(row=1, column=3, padx=5, pady=3, sticky=tk.W)
        self.scan_rate_entry.insert(0, "1000")
        
        # 扫描类型选择
        ttk.Label(input_frame, text="扫描工具：").grid(row=2, column=0, padx=5, pady=3, sticky=tk.W)
        self.scan_tool_var = tk.StringVar(value="auto")
        scan_tools = ttk.Combobox(input_frame, textvariable=self.scan_tool_var, values=["auto", "masscan", "nmap"], width=10)
        scan_tools.grid(row=2, column=1, padx=5, pady=3, sticky=tk.W)
        
        # 指纹识别选项
        self.fingerprint_var = tk.BooleanVar(value=True)
        ttk.Checkbutton(input_frame, text="启用指纹识别", variable=self.fingerprint_var).grid(
            row=2, column=2, padx=5, pady=3, sticky=tk.W
        )
        
        # 扫描控制
        control_frame = ttk.Frame(tab)
        control_frame.pack(fill=tk.X, padx=5, pady=5)
        
        ttk.Button(control_frame, text="开始扫描", command=self._start_port_scan).grid(
            row=0, column=0, padx=10, pady=5
        )
        ttk.Button(control_frame, text="停止扫描", command=self._stop_port_scan).grid(
            row=0, column=1, padx=10, pady=5
        )
        
        # 扫描结果
        result_frame = ttk.Frame(tab)
        result_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        ttk.Label(result_frame, text="扫描结果：").pack(anchor=tk.W, padx=2, pady=2)
        self.scan_result_table = ttk.Treeview(
            result_frame, 
            columns=("target", "port", "status", "service", "version", "fingerprint"), 
            show="headings"
        )
        for col in self.scan_result_table["columns"]:
            self.scan_result_table.heading(col, text=col.upper(), anchor=tk.W)
            width = 120 if col != "fingerprint" else 200
            self.scan_result_table.column(col, width=width, anchor=tk.W)
        
        scan_scrollbar = ttk.Scrollbar(result_frame, orient=tk.VERTICAL, command=self.scan_result_table.yview)
        self.scan_result_table.configure(yscrollcommand=scan_scrollbar.set)
        scan_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.scan_result_table.pack(fill=tk.BOTH, expand=True)

    def _start_port_scan(self):
        """开始端口扫描（集成masscan/nmap）"""
        targets = self.scan_target_entry.get().split(",")
        targets = [t.strip() for t in targets if t.strip()]
        port_range = self.port_range_entry.get().strip()
        scan_rate = self.scan_rate_entry.get().strip()
        scan_tool = self.scan_tool_var.get()
        use_fingerprint = self.fingerprint_var.get()
        
        if not targets or not port_range or not scan_rate:
            messagebox.showwarning("警告", "请输入目标、端口范围和扫描速率")
            return
            
        try:
            scan_rate = int(scan_rate)
            if scan_rate <= 0:
                raise ValueError
        except ValueError:
            messagebox.showwarning("警告", "扫描速率必须是正整数")
            return
            
        # 清空现有结果
        for item in self.scan_result_table.get_children():
            self.scan_result_table.delete(item)
            
        self.status_var.set(f"开始端口扫描，目标: {', '.join(targets)}")
        self.port_scanner.scanning = True
        
        # 执行扫描
        def scan():
            try:
                # 对每个目标执行扫描
                for target in targets:
                    if not self.port_scanner.scanning:
                        break
                        
                    self.status_var.set(f"正在扫描目标: {target}")
                    
                    # 根据选择的工具执行扫描
                    if scan_tool == "masscan" and not self.port_scanner._check_command_exists('masscan'):
                        messagebox.showwarning("警告", "未检测到masscan，请安装后再试")
                        break
                        
                    if scan_tool == "nmap" and not self.port_scanner._check_command_exists('nmap'):
                        messagebox.showwarning("警告", "未检测到nmap，请安装后再试")
                        break
                        
                    # 执行扫描
                    results = self.port_scanner.scan_ports(
                        target=target,
                        ports=port_range,
                        rate=scan_rate
                    )
                    
                    # 处理扫描结果
                    for result in results:
                        if 'error' in result:
                            self.scan_result_table.insert("", tk.END, values=(
                                target, "", "错误", result['error'], "", ""
                            ))
                            continue
                            
                        # 指纹识别
                        fingerprint_info = "未启用"
                        if use_fingerprint and result['status'] == 'open':
                            fp_result = self.port_scanner.identify_fingerprint(
                                target=result['target'],
                                port=result['port']
                            )
                            
                            if fp_result['technologies']:
                                fingerprint_info = ", ".join(fp_result['technologies'])
                            elif fp_result['error']:
                                fingerprint_info = f"识别失败: {fp_result['error']}"
                            else:
                                fingerprint_info = "未识别到"
                        
                        # 插入结果到表格
                        self.scan_result_table.insert("", tk.END, values=(
                            result['target'],
                            result['port'],
                            result['status'],
                            result['service'],
                            result.get('version', '未知'),
                            fingerprint_info
                        ))
                
                if self.port_scanner.scanning:
                    self.status_var.set(f"端口扫描完成，目标: {', '.join(targets)}")
                else:
                    self.status_var.set(f"端口扫描已停止")
                    
            except Exception as e:
                self.scan_result_table.insert("", tk.END, values=(
                    "", "", "错误", str(e), "", ""
                ))
                self.status_var.set(f"扫描出错: {str(e)}")
            finally:
                self.port_scanner.scanning = False
        
        threading.Thread(target=scan, daemon=True).start()

    def _stop_port_scan(self):
        """停止端口扫描"""
        if self.port_scanner.scanning:
            self.port_scanner.stop_scan()
            self.status_var.set("正在停止端口扫描...")

    # ------------------------------ 系统设置模块 ------------------------------
    def _init_system_setting_tab(self):
        """系统设置模块UI"""
        tab = ttk.Frame(self.notebook)
        self.notebook.add(tab, text="系统设置")
        
        # 代理设置
        proxy_frame = ttk.LabelFrame(tab, text="代理设置")
        proxy_frame.pack(fill=tk.X, padx=10, pady=10)
        
        self.proxy_enable_var = tk.BooleanVar(value=self.proxy_config["enable"])
        ttk.Checkbutton(proxy_frame, text="启用代理", variable=self.proxy_enable_var).grid(
            row=0, column=0, padx=5, pady=5, sticky=tk.W
        )
        
        ttk.Label(proxy_frame, text="代理主机：").grid(row=1, column=0, padx=5, pady=3, sticky=tk.W)
        self.proxy_host_entry = ttk.Entry(proxy_frame, width=30)
        self.proxy_host_entry.grid(row=1, column=1, padx=5, pady=3, sticky=tk.W)
        self.proxy_host_entry.insert(0, self.proxy_config["host"])
        
        ttk.Label(proxy_frame, text="代理端口：").grid(row=1, column=2, padx=5, pady=3, sticky=tk.W)
        self.proxy_port_entry = ttk.Entry(proxy_frame, width=10)
        self.proxy_port_entry.grid(row=1, column=3, padx=5, pady=3, sticky=tk.W)
        self.proxy_port_entry.insert(0, self.proxy_config["port"])
        
        ttk.Label(proxy_frame, text="用户名：").grid(row=2, column=0, padx=5, pady=3, sticky=tk.W)
        self.proxy_user_entry = ttk.Entry(proxy_frame, width=30)
        self.proxy_user_entry.grid(row=2, column=1, padx=5, pady=3, sticky=tk.W)
        self.proxy_user_entry.insert(0, self.proxy_config["user"])
        
        ttk.Label(proxy_frame, text="密码：").grid(row=2, column=2, padx=5, pady=3, sticky=tk.W)
        self.proxy_pwd_entry = ttk.Entry(proxy_frame, width=30, show="*")
        self.proxy_pwd_entry.grid(row=2, column=3, padx=5, pady=3, sticky=tk.W)
        self.proxy_pwd_entry.insert(0, self.proxy_config["pwd"])
        
        ttk.Button(proxy_frame, text="测试代理", command=self._test_proxy).grid(
            row=3, column=0, padx=5, pady=5
        )
        ttk.Button(proxy_frame, text="保存代理设置", command=self._save_proxy).grid(
            row=3, column=1, padx=5, pady=5
        )
        
        # 授权信息
        auth_frame = ttk.LabelFrame(tab, text="授权信息")
        auth_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(auth_frame, text="机器码：").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        self.machine_code_var = tk.StringVar(value="******-****-****-****-******")
        ttk.Label(auth_frame, textvariable=self.machine_code_var).grid(
            row=0, column=1, padx=5, pady=3, sticky=tk.W
        )
        
        ttk.Label(auth_frame, text="授权码：").grid(row=1, column=0, padx=5, pady=3, sticky=tk.W)
        self.auth_code_entry = ttk.Entry(auth_frame, width=50, show="*")
        self.auth_code_entry.grid(row=1, column=1, padx=5, pady=3, sticky=tk.W)
        
        ttk.Button(auth_frame, text="验证授权", command=self._verify_auth).grid(
            row=1, column=2, padx=5, pady=3
        )
        
        # 版本信息
        version_frame = ttk.LabelFrame(tab, text="版本信息")
        version_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Label(version_frame, text="当前版本：v1.0").grid(row=0, column=0, padx=5, pady=3, sticky=tk.W)
        ttk.Label(version_frame, text="更新日期：2023-10-15").grid(row=1, column=0, padx=5, pady=3, sticky=tk.W)
        ttk.Button(version_frame, text="检查更新", command=self._check_update).grid(
            row=0, column=1, padx=5, pady=3
        )

    def _test_proxy(self):
        """测试代理连接"""
        self.status_var.set("正在测试代理连接...")
        
        def test():
            time.sleep(1.5)
            if random.random() > 0.3:
                messagebox.showinfo("测试结果", "代理连接成功")
                self.status_var.set("代理连接测试成功")
            else:
                messagebox.showerror("测试结果", "代理连接失败，请检查设置")
                self.status_var.set("代理连接测试失败")
                
        threading.Thread(target=test, daemon=True).start()

    def _save_proxy(self):
        """保存代理设置"""
        self.proxy_config = {
            "enable": self.proxy_enable_var.get(),
            "host": self.proxy_host_entry.get(),
            "port": self.proxy_port_entry.get(),
            "user": self.proxy_user_entry.get(),
            "pwd": self.proxy_pwd_entry.get()
        }
        self.status_var.set("代理设置已保存")

    def _verify_auth(self):
        """验证授权码"""
        auth_code = self.auth_code_entry.get()
        if not auth_code:
            messagebox.showwarning("警告", "请输入授权码")
            return
            
        self.status_var.set("正在验证授权码...")
        
        def verify():
            time.sleep(1.5)
            if len(auth_code) >= 16:
                messagebox.showinfo("验证结果", "授权码验证成功")
                self.status_var.set("授权验证成功")
            else:
                messagebox.showerror("验证结果", "授权码无效，请检查")
                self.status_var.set("授权验证失败")
                
        threading.Thread(target=verify, daemon=True).start()

    def _check_update(self):
        """检查更新"""
        self.status_var.set("正在检查更新...")
        
        def check():
            time.sleep(1.5)
            messagebox.showinfo("更新检查", "当前已是最新版本 v1.0")
            self.status_var.set("更新检查完成")
            
        threading.Thread(target=check, daemon=True).start()

    # ------------------------------ 菜单功能实现 ------------------------------
    def _export_data(self):
        """导出数据"""
        current_tab = self.notebook.select()
        tab_text = self.notebook.tab(current_tab, "text")
        
        if tab_text == "网络资产测绘":
            messagebox.showinfo("导出数据", "网络资产数据已导出为CSV文件")
        elif tab_text == "小程序分析":
            # 如果在小程序分析标签页，直接调用导出功能
            self._export_applet_results()
        elif tab_text == "备案与企业查询":
            messagebox.showinfo("导出数据", "备案与企业查询数据已导出为Excel文件")
        elif tab_text == "端口扫描与指纹识别":
            messagebox.showinfo("导出数据", "端口扫描结果已导出为CSV文件")
        else:
            messagebox.showinfo("导出数据", "请先选择要导出数据的模块")
            
        self.status_var.set("数据导出完成")

    def _clear_cache(self):
        """清除缓存"""
        if messagebox.askyesno("确认", "确定要清除所有缓存数据吗？"):
            self.status_var.set("正在清除缓存...")
            
            def clear():
                time.sleep(1)
                messagebox.showinfo("完成", "缓存数据已清除")
                self.status_var.set("缓存已清除")
                
            threading.Thread(target=clear, daemon=True).start()

    def _show_decode_tool(self):
        """显示编解码工具"""
        decode_window = tk.Toplevel(self)
        decode_window.title("编解码工具")
        decode_window.geometry("600x400")
        decode_window.transient(self)
        decode_window.grab_set()
        
        ttk.Label(decode_window, text="编解码工具功能正在开发中...").pack(pady=20)
        ttk.Button(decode_window, text="关闭", command=decode_window.destroy).pack(pady=10)

    def _open_urls_in_browser(self):
        """浏览器批量打开URL"""
        url_window = tk.Toplevel(self)
        url_window.title("批量打开URL")
        url_window.geometry("600x400")
        url_window.transient(self)
        
        ttk.Label(url_window, text="输入URL（每行一个）：").pack(anchor=tk.W, padx=10, pady=5)
        url_text = scrolledtext.ScrolledText(url_window, wrap=tk.WORD)
        url_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        def open_urls():
            urls = url_text.get(1.0, tk.END).strip().split("\n")
            urls = [u for u in urls if u.strip()]
            
            if not urls:
                messagebox.showwarning("警告", "请输入URL")
                return
                
            for url in urls:
                webbrowser.open(url)
                time.sleep(0.5)
                
            messagebox.showinfo("完成", f"已在浏览器中打开{len(urls)}个URL")
            url_window.destroy()
            
        ttk.Button(url_window, text="打开URL", command=open_urls).pack(pady=10)

    def _show_version(self):
        """显示版本信息"""
        version_info = """minisecret-v1.0

更新说明：
- 集成URL/路由/API提取功能（jsfinder）
- 增强敏感信息检测与URL提取联动
- 优化扫描性能和结果展示
- 添加结果导出功能
- 修复已知BUG

支持平台：
- Windows x64/arm64
- macOS x64/arm64

部分功能采取订阅制：
- ICP批量查询
- 爱企查导出
- 小程序敏感信息提取

其余功能不受限
"""
        messagebox.showinfo("版本信息", version_info)

if __name__ == "__main__":
    # 禁用HTTPS证书验证警告
    import urllib3
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    app = MiniTool()
    app.mainloop()
